/*
 * AtomResource.java
 *
 * Created on March 27, 2007, 11:48 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;
import org.atomojo.app.db.EntryMedia;
import org.atomojo.app.db.Feed;
import org.infoset.xml.Child;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.restlet.Application;
import org.restlet.data.MediaType;
import org.restlet.resource.ServerResource;

/**
 *
 * @author alex
 */
public class AtomResource extends ServerResource {
   
   public static final MediaType ATOM_MEDIA_TYPE = MediaType.valueOf("application/atom+xml");
   public static final URI APP_NAMESPACE = URI.create("http://www.w3.org/2007/app");
   public static final URI ATOM_NAMESPACE = URI.create("http://www.w3.org/2005/Atom");
   public static final Name FEED_NAME = Name.create(ATOM_NAMESPACE,"feed");
   public static final Name ENTRY_NAME = Name.create(ATOM_NAMESPACE,"entry");
   public static final Name TITLE_NAME = Name.create(ATOM_NAMESPACE,"title");
   public static final Name SUMMARY_NAME = Name.create(ATOM_NAMESPACE,"summary");
   public static final Name ID_NAME = Name.create(ATOM_NAMESPACE,"id");
   public static final Name LINK_NAME = Name.create(ATOM_NAMESPACE,"link");
   public static final Name CONTENT_NAME = Name.create(ATOM_NAMESPACE,"content");
   public static final Name PUBLISHED_NAME = Name.create(ATOM_NAMESPACE,"published");
   public static final Name UPDATED_NAME = Name.create(ATOM_NAMESPACE,"updated");
   public static final Name EDITED_NAME = Name.create(APP_NAMESPACE,"edited");
   public static final Name AUTHOR_NAME = Name.create(ATOM_NAMESPACE,"author");
   public static final Name CATEGORY_NAME = Name.create(ATOM_NAMESPACE,"category");
   public static final Name NAME_NAME = Name.create(ATOM_NAMESPACE,"name");
   public static final Name CATEGORIES_NAME = Name.create(APP_NAMESPACE,"categories");
   public static MediaType XQUERY_TYPE = MediaType.valueOf("application/xquery");

   static DateFormat getDateAndTimeFormatter() {
      return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
   }
   
   public static String toXSDDate(Date d) {
      DateFormat format = getDateAndTimeFormatter();
      String v = format.format(d);
      return v.substring(0,22)+':'+v.substring(22);
   }
   
   public static Date fromXSDDate(String value) 
      throws java.text.ParseException
   {
      if (value.length()<22) {
         throw new java.text.ParseException("Truncated dateTime value.",value.length());
      }
      DateFormat format = getDateAndTimeFormatter();
      String wocolon = value.substring(0,22)+value.substring(23);
      return format.parse(wocolon);
   }
   
   protected Application theApp;
   protected Storage storage;
   protected Feed feed;
   
   /** Creates a new instance of AtomResource */
   public AtomResource(Application app,Feed feed, Storage storage) {
      this.theApp = app;
      this.feed = feed;
      this.storage = storage;
      setNegotiated(false);
   }
   
   public Feed getFeed() {
      return feed;
   }
   
   public Storage getStorage() {
      return storage;
   }
   
   public static Document createFeedDocument(String title,UUID id,Date created)
      throws XMLException
   {
      ItemConstructor constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
      Document doc = constructor.createDocument();
      Element top = doc.createDocumentElement(AtomResource.FEED_NAME);
      top.addNamespaceBinding("app",APP_NAMESPACE);
      top.addElement(TITLE_NAME).addCharacters(title);
      top.addElement(ID_NAME).addCharacters("urn:uuid:"+id.toString());
      String dateString = toXSDDate(created);
      //top.addElement(PUBLISHED_NAME).addCharacters(dateString);
      top.addElement(UPDATED_NAME).addCharacters(dateString);
      //top.addElement(EDITED_NAME).addCharacters(dateString);
      Element link = top.addElement(LINK_NAME);
      link.setAttributeValue("rel","self");
      link.setAttributeValue("href","");
      link = top.addElement(LINK_NAME);
      link.setAttributeValue("rel","edit");
      link.setAttributeValue("href","");
      return doc;
   }
   
   public static Document createEntryDocument(String title,UUID id,Date created,String authorName)
      throws XMLException
   {
      ItemConstructor constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
      Document doc = constructor.createDocument();
      Element top = doc.createDocumentElement(AtomResource.ENTRY_NAME);
      top.addElement(TITLE_NAME).addCharacters(title);
      top.addElement(ID_NAME).addCharacters("urn:uuid:"+id.toString());
      String dateString = toXSDDate(created);
      top.addElement(PUBLISHED_NAME).addCharacters(dateString);
      top.addElement(UPDATED_NAME).addCharacters(dateString);
      Element edited = top.addElement(EDITED_NAME);
      edited.addCharacters(dateString);
      edited.setPrefix("app");
      Element link = top.addElement(LINK_NAME);
      link.setAttributeValue("rel","edit");
      link.setAttributeValue("href","./_/"+id);
      if (authorName!=null) {
         Element author = top.addElement(AUTHOR_NAME);
         author.addElement(NAME_NAME).addCharacters(authorName);
      }
      return doc;
   }
   public static Document createMediaEntryDocument(String title,UUID id,Date created,String authorName,String href,MediaType type)
      throws XMLException
   {
      Document entryDoc = createEntryDocument(title,id,created,authorName);
      Element top = entryDoc.getDocumentElement();
      Element link = top.addElement(LINK_NAME);
      link.setAttributeValue("rel","edit-media");
      link.setAttributeValue("href",href);
      Element content = top.addElement(CONTENT_NAME);
      content.setAttributeValue("src",href);
      content.setAttributeValue("type",type.toString());
      return entryDoc;
   }
   
   public static void mergeFeedDocument(Document doc,UUID id,Date created,Date modified)
   {
      boolean idDone = false;
      boolean createdDone = false;
      boolean modifiedDone = false;
      Element feed = doc.getDocumentElement();
      List<Child> toRemove = new ArrayList<Child>();
      Element linkEdit = null;
      for (Child c : feed) {
         if (c instanceof Element) {
            Element e = (Element)c;
            if (e.getName().equals(ID_NAME)) {
               idDone = true;
               e.clear();
               e.addCharacters("urn:uuid:"+id.toString());
               /*
            } else if (e.getName().equals(PUBLISHED_NAME) && created!=null) {
               createdDone = true;
               e.clear();
               e.addCharacters(toXSDDate(created));
                */
            } else if (e.getName().equals(LINK_NAME)) {
               String rel = e.getAttributeValue("rel");
               if ("edit".equals(rel)) {
                  linkEdit = e;
               } else if ("self".equals(rel)) {
                  toRemove.add(e);
               }
            } else if (e.getName().equals(UPDATED_NAME)) {
               modifiedDone = true;
               e.clear();
               e.addCharacters(toXSDDate(modified));
            } else if (e.getName().equals(ENTRY_NAME)) {
               toRemove.add(e);
            }
         }
      }
      if (!modifiedDone) {
         feed.addElement(0,UPDATED_NAME).addCharacters(toXSDDate(modified));
      }
      /*
      if (!createdDone  && created!=null) {
         feed.addElement(0,PUBLISHED_NAME).addCharacters(toXSDDate(created));
      }
       */
      if (!idDone) {
         feed.addElement(0,ID_NAME).addCharacters("urn:uuid:"+id.toString());
      }
      feed.removeAll(toRemove);
      if (linkEdit==null) {
         linkEdit = feed.addElement(LINK_NAME);
      }
      linkEdit.setAttributeValue("rel","edit");
      linkEdit.setAttributeValue("href","");
      Element selfLink = feed.addElement(LINK_NAME);
      selfLink.setAttributeValue("rel","self");
      selfLink.setAttributeValue("href","");
   }
   
   public static void mergeEntry(Element entry,UUID id,Date created,Date modified,String authorName,EntryMedia media)
   {
      boolean idDone = false;
      boolean createdDone = false;
      boolean modifiedDone = false;
      boolean editedDone = false;
      List<Child> toRemove = new ArrayList<Child>();
      Element linkEdit = null;
      Element linkEditMedia = null;
      Element content = null;
      Element author = null;
      for (Child c : entry) {
         if (c instanceof Element) {
            Element e = (Element)c;
            if (e.getName().equals(ID_NAME)) {
               idDone = true;
               e.clear();
               e.addCharacters("urn:uuid:"+id.toString());
            } else if (e.getName().equals(PUBLISHED_NAME) && created!=null) {
               createdDone = true;
               /*
               e.clear();
               e.addCharacters(toXSDDate(created));
                */
            } else if (e.getName().equals(UPDATED_NAME)) {
               modifiedDone = true;
            } else if (e.getName().equals(EDITED_NAME)) {
               editedDone = true;
               e.clear();
               e.setPrefix("app");
               e.addCharacters(toXSDDate(modified));
            } else if (e.getName().equals(AUTHOR_NAME)) {
               author = e;
            } else if (e.getName().equals(LINK_NAME)) {
               String rel = e.getAttributeValue("rel");
               if ("edit".equals(rel)) {
                  linkEdit = e;
               } else if ("self".equals(rel)) {
                  toRemove.add(e);
               } else if ("edit-media".equals(rel)) {
                  if (media!=null) {
                     linkEditMedia = e;
                  } else {
                     toRemove.add(e);
                  }
               }
            } else if (e.getName().equals(CONTENT_NAME)) {
               content = e;
            }
         }
      }
      if (!modifiedDone) {
         entry.addElement(0,UPDATED_NAME).addCharacters(toXSDDate(modified));
      }
      if (!editedDone) {
         Element e = entry.addElement(0,EDITED_NAME);
         e.addCharacters(toXSDDate(modified));
         e.setPrefix("app");
      }
      if (!createdDone && created!=null) {
         entry.addElement(0,PUBLISHED_NAME).addCharacters(toXSDDate(created));
      }
      if (!idDone) {
         entry.addElement(0,ID_NAME).addCharacters("urn:uuid:"+id.toString());
      }
      entry.removeAll(toRemove);
      if (linkEdit==null) {
         linkEdit = entry.addElement(LINK_NAME);
      }
      linkEdit.setAttributeValue("rel","edit");
      linkEdit.setAttributeValue("href","./_/"+id);
      if (authorName!=null && author==null) {
         author = entry.addElement(AUTHOR_NAME);
         author.addElement(NAME_NAME).addCharacters(authorName);
      }
      if (media!=null) {
         if (linkEditMedia==null) {
            linkEditMedia = entry.addElement(LINK_NAME);
         }
         linkEditMedia.setAttributeValue("rel","edit-media");
         linkEditMedia.setAttributeValue("href",media.getName());
         if (content==null) {
            content = entry.addElement(CONTENT_NAME);
         }
         content.setAttributeValue("type",media.getMediaType().getName());
         content.setAttributeValue("src",media.getName());
      }
   }
   
   public static String getValue(Element e) {
      String value = e.getText();
      if (value!=null) {
         value = value.trim();
         if (value.length()==0) {
            value = null;
         }
      }
      if (value==null) {
         value = e.getAttributeValue("value");
      }
      return value;
   }
   
   public static EntryIndex indexEntry(Logger log,Element entry)
   {
      EntryIndex index = new EntryIndex();
      for (Child c : entry) {
         if (c.getType()!=Item.ItemType.ElementItem) {
            continue;
         }
         Element e = (Element)c;
         Name name = e.getName();
         //Logger.getAnonymousLogger().info("Name: "+name);
         if (name.equals(ID_NAME)) {
            String value = e.getText().trim();
            //Logger.getAnonymousLogger().info("id: "+value);
            if (value.startsWith("urn:uuid:")) {
               index.setId(UUID.fromString(value.substring(9)));
            }
         } else if (name.equals(AUTHOR_NAME)) {
            Iterator<Element> children = e.getElementsByName(NAME_NAME);
            if (children.hasNext()) {
               String authorName = children.next().getText().trim();
               if (authorName.length()>0) {
                  index.setAuthorName(authorName);
               }
            }
         } else if (name.equals(CATEGORY_NAME)) {
            String scheme = e.getAttributeValue("scheme");
            String term = e.getAttributeValue("term");
            if (term!=null) {
               term = term.trim();
               if (term.length()==0) {
                  term = null;
               } else {
                  try {
                     term = URLEncoder.encode(term,"UTF-8");
                  } catch (UnsupportedEncodingException ex) {
                     log.severe("Cannot encode term due to: "+ex.getMessage());
                     continue;
                  }
               }
            }
            if (term==null) {
               continue;
            }
            if (scheme!=null) {
               scheme = scheme.trim();
               if (scheme.length()==0) {
                  scheme =null;
               }
            }
            String value = getValue(e);
            URI uscheme = null;
            if (scheme!=null) {
               uscheme = URI.create(scheme);
            } else {
               uscheme = Categorization.DEFAULT_SCHEME;
            }
            URI uterm = uscheme.resolve(term).normalize();
            if (value==null) {
               index.categorize(uterm);
            } else {
               index.put(uterm,value);
            }
         }
      }
      return index;
   }
   
   public static FeedIndex indexFeed(Logger log,Element feed)
   {
      FeedIndex index = new FeedIndex();
      for (Child c : feed) {
         if (c.getType()!=Item.ItemType.ElementItem) {
            continue;
         }
         Element e = (Element)c;
         Name name = e.getName();
         if (name.equals(ID_NAME)) {
            String value = e.getText().trim();
            if (value.startsWith("urn:uuid:")) {
               index.setId(UUID.fromString(value.substring(9)));
            }
         } else if (name.equals(CATEGORY_NAME)) {
            String scheme = e.getAttributeValue("scheme");
            String term = e.getAttributeValue("term");
            if (term!=null) {
               term = term.trim();
               if (term.length()==0) {
                  term = null;
               } else {
                  try {
                     term = URLEncoder.encode(term,"UTF-8");
                  } catch (UnsupportedEncodingException ex) {
                     log.severe("Cannot encode term due to: "+ex.getMessage());
                     continue;
                  }
               }
            }
            if (term==null) {
               continue;
            }
            String value = getValue(e);
            if (scheme!=null) {
               scheme = scheme.trim();
               if (scheme.length()==0) {
                  scheme =null;
               }
            }
            URI uscheme = null;
            if (scheme!=null) {
               uscheme = URI.create(scheme);
            } else {
               uscheme = Categorization.DEFAULT_SCHEME;
            }
            URI uterm = uscheme.resolve(term).normalize();
            if (value==null) {
               index.categorize(uterm);
            } else {
               index.put(uterm,value);
            }
         }
      }
      return index;
   }
}
